#include "lib.h"

#include "csgame.h"
#include "CPokerConnection.h"
#include "shue.h"
#include "cshand.h"
#include "cardmgr.h"
#include "csranks.h"
#include "CSavedGame.h"

#define CSTUD_NUMDECKS    1        // num decks in shue

static CardMgr stdMgr;

static int32 sPayTable[CStudGame::MAX_CSTUD_PAY_FREQUENCIES][HR_ROYALFLUSH + 1] =
{
    {   1, 1, 2, 3, 4, 5, 7, 20, 50, 100   },     //LOW pay table

    {   1, 1, 2, 3, 4, 5, 7, 20, 50, 200   },     //MEDIUM pay table

    {   1, 1, 1, 3, 5, 9, 14, 100, 200, 1000   }, //HIGH pay table
};

static int32 sProgressivePayTable[CStudGame::MAX_CSTUD_PROGRESSIVE_PAY_FREQUENCIES][HR_ROYALFLUSH + 1] =
{
    {   0, 0, 0, 0, 0, 5000, 7500, 10000, 10, 100   },   // LOW progressive pay (frequency) table

    {   0, 0, 0, 0, 0, 5000, 10000, 50000, 10, 100   },   // MEDIUM progressive pay (frequency) table

    {   0, 0, 0, 0, 0, 7500, 15000, 50000, 10, 100   },   // HIGH progressive pay (frequency) table

    {   0, 0, 0, 0, 0, 7500, 10000, 50000, 10, 100   }   // ALTHIGH1 progressive pay (frequency) table
};


CStudGame::CStudGame(CPokerConnection* conn, int32 bet, int32 progressiveBet)
{
    this->conn = conn;
    this->bet = bet;
    this->progressiveBet = progressiveBet;

    dealer = new CStudHand(&stdMgr);
    player = new CStudHand(&stdMgr);

    payTable = conn->getPayTable();
    progPayTable = conn->getProgressivePayTable();

    bGameOK = true;
}

CStudGame::CStudGame(CPokerConnection* conn, CSavedGame* saved)
{
    this->conn = conn;

    // everything else is restored in restore()
    //
    bet = 0;
    dealer = player = (CStudHand*)0;

    bGameOK = restore(saved);
}

CStudGame::~CStudGame()
{
    delete dealer;
    delete player;
}


// Rudy 12/206/042: Added this block to allow QA to test
#if (_DEBUG && MAGIC_DECK)

// Deck offsets (feel the thread-unsafeness)
int32 iPlayer = 0;
int32 iDealer = 0;

// Loaded player deck
static const uint8 puPlayerCards[] = {
    0x4a, 0x4b, 0x4c, 0x4d,    0x4e,    // royal flush
    0x37, 0x36, 0x35, 0x34, 0x33,    // straight flush
    0x23, 0x33, 0x43, 0x33, 0x24,    // 4 of a kind
    0x23, 0x13, 0x43, 0x34, 0x24,    // full house
    0x33, 0x39, 0x3b, 0x35, 0x38,    // flush
    0x27, 0x46, 0x35, 0x14, 0x33,    // straight 
    0x17, 0x37, 0x47, 0x45, 0x1b,    // 3 of kind
    0x39, 0x19, 0x48, 0x28, 0x24,    // 3 of kind
    0x2d, 0x3d, 0x4b, 0x17, 0x28,    // one pair
    0x3e, 0x4d, 0x2a, 0x15, 0x42    // high card
};

// Loaded dealer deck
static const uint8 puDealerCards[] = {
    0x33, 0x39, 0x4b, 0x1d, 0x2e,    
    0x33, 0x39, 0x4b, 0x1d, 0x3e,
    0x35, 0x25, 0x43, 0x2a, 0x1d,    
    0x33, 0x39, 0x4e, 0x15, 0x4d,
    0x33, 0x39, 0x4d, 0x15, 0x4e,
    0x33, 0x39, 0x4d, 0x15, 0x1e,
    0x33, 0x39, 0x4b, 0x15, 0x2d,
    0x33, 0x39, 0x4e, 0x15, 0x3d,
    0x32, 0x12, 0x1c, 0x23, 0x46,
    0x4d, 0x2e, 0x37, 0x16, 0x45
};

// Get the next card from the player deck
uint8 getPlayerCard()
{
    uint8 uCard = puPlayerCards[iPlayer];
    if (++iPlayer >= sizeof(puPlayerCards)/sizeof(uint8))
        iPlayer = 0;
    return uCard;
}

// Get the next card from the dealer deck
uint8 getDealerCard()
{
    uint8 uCard = puDealerCards[iDealer];
    if (++iDealer >= sizeof(puDealerCards)/sizeof(uint8))
        iDealer = 0;
    return uCard;
}

#endif


bool8
CStudGame::deal(int32 balance)
{
    Shue* shue;
    bool8 result = false;

	ShueGameInfo sgi;
	sgi.gameId = CARIBBEAN_STUD;
	sgi.machId = 0;

     #ifdef FORCEDSHOE 
       sgi.pid=conn->GetPID();
    #endif


    shue = new Shue(&stdMgr, CSTUD_NUMDECKS, sgi);

    if (shue && dealer && player)
    {
        shue->shuffle();

        // kharmon.  10-12-06.  #8226.  Save the seed so that we can regenerate the cards on a saved game.
        ShueSave save;
        shue->save(&save);
        m_nDeckSeed = save.seed;

#if (_DEBUG && MAGIC_DECK)
        player->add(getPlayerCard());
        dealer->add(getDealerCard());

        player->add(getPlayerCard());
        dealer->add(getDealerCard());

        player->add(getPlayerCard());
        dealer->add(getDealerCard());

        player->add(getPlayerCard());
        dealer->add(getDealerCard());

        player->add(getPlayerCard());
        dealer->add(getDealerCard());
#else
        player->add(shue->getCard());
        dealer->add(shue->getCard());

        player->add(shue->getCard());
        dealer->add(shue->getCard());

        player->add(shue->getCard());
        dealer->add(shue->getCard());

        player->add(shue->getCard());
        dealer->add(shue->getCard());

        player->add(shue->getCard());
        dealer->add(shue->getCard());
#endif

        delete shue;

        if (conn->getDBInterface()->trackDeal(this))
        {
            conn->msgStart();
            conn->msgAdd((uint8)'D');
            conn->msgReleaseBuf(dealer->outlast(conn->msgGetBuf()));
            conn->msgAdd((uint8)(player->getCount() & 0xff));
            conn->msgReleaseBuf(player->out(conn->msgGetBuf()));
            conn->msgAdd(balance);
            conn->msgEnd();

            result = true;
        }
    }

    return result;
}

bool8
CStudGame::raise(int32 balance, uint8 *)
{
    return finish(true, balance);
}

bool8
CStudGame::fold(int32 balance)
{
    return finish(false, balance);
}

bool8
CStudGame::finish(bool8 raised, int32 balance)
{
    int32 payouts[2], balAdjust, progPayout;
    int32 multiBankPayout = 0;
    int32 outcome;
    bool8 useHighCard;

#if 0 // JDH 05/30/01:  progressive only turned on/off between **sessions**
      //                removing this helps combat problems when it or
      //                    trackGameDone fails (among other complications)
      //
    if (!conn->trackGetOptions())
        return false;
#endif

    // luke 3/7/02: This now returns an error when a progressive win fails
    if (!calcPayouts(payouts, &progPayout, &multiBankPayout, raised, &useHighCard, &outcome, &balAdjust))
        return false;

    if (conn->getDBInterface()->trackGameDone(raised, payouts[0], payouts[1], progPayout, 
                                              multiBankPayout, outcome, balAdjust, &balance, 
                                              hasMalfunction(), this))
    {
        conn->msgStart();
        conn->msgAdd((uint8)'F');
        conn->msgAdd((uint8)(dealer->getCount() & 0xff));
        conn->msgReleaseBuf(dealer->out(conn->msgGetBuf()));
        conn->msgAdd(payouts[0]);
        conn->msgAdd(payouts[1]);
        conn->msgAdd(progPayout);
        conn->msgReleaseBuf(dealer->outrank(conn->msgGetBuf()));
        conn->msgReleaseBuf(dealer->outhigh(conn->msgGetBuf()));
        conn->msgReleaseBuf(player->outrank(conn->msgGetBuf()));
        conn->msgReleaseBuf(player->outhigh(conn->msgGetBuf()));
        conn->msgAdd((uint8)(outcome & 0xff));
        conn->msgAdd((uint8)(useHighCard & 0xff));
        conn->msgAdd(balance);
        conn->msgAdd((uint8)conn->getPayTable());
        conn->msgAdd((uint8)conn->isProgressiveOn());
        conn->msgAdd((uint8)conn->getProgressivePayTable());
        conn->msgEnd();

        // kharmon.  01-05-06.  #4250.  If the player hit a big progressive jackpot, 
        // this error will shut them down until we can verify things.
        if(hasMalfunction())
        {
            conn->msgStart();
            conn->msgAdd((uint8) 'E');
            conn->msgAdd((uint8) 13);
            conn->msgEnd();
        }

        conn->gameDone();    // delete "this"

        return true;
    }

    return false;
}


bool8
CStudGame::calcPayouts(int32* payouts, int32* progressivePayout, int32* multiBankPayout, 
                       bool8 raised, bool8* useHighCard, int32* outcome, int32* balAdjust)
{
    int32 winnings;

    //
    // Return dealer & player hand ranks & high card regardless of whether
    // player folded or not.  Although, not necessary when player folds, it's
    // nice to see what the dealer had (and how it would've ranked).
    //
    if (!dealer->rankHand())
        return false;
    dealer->calcNthHighest(0);

    if (!player->rankHand())
        return false;
    player->calcNthHighest(0);

    // kharmon.  11-22-05.  #7534, 7550.  Don't show tie card unless the hand ranks are the same.
    *useHighCard = false;

    if (dealer->getRank() > player->getRank())
        *outcome = O_DEALER_WON;
    else if (dealer->getRank() < player->getRank())
        *outcome = O_PLAYER_WON;
    else
    {
        int32 i;

        // kharmon.  11-22-05.  #7534, 7550.  Hmmmm...same rank, it may come down to a kicker.
        *useHighCard = true;

        *outcome = O_PUSH;

        for (i = 0; i < dealer->getCount(); i++)
        {
            if (i > 0)                     //0th high card already calculated above
            {
                dealer->calcNthHighest(i);
                player->calcNthHighest(i);
            }

            if (dealer->getHighCard() > player->getHighCard())
            {
                *outcome = O_DEALER_WON;
                break;
            }
            else if (dealer->getHighCard() < player->getHighCard())
            {
                *outcome = O_PLAYER_WON;
                break;
            }
        }
    }

    ASSERT(progPayTable >= 0 && progPayTable < MAX_CSTUD_PROGRESSIVE_PAY_FREQUENCIES &&
        player->getRank() >= 0 && player->getRank() <= HR_ROYALFLUSH);

    if (progressiveBet > 0 && sProgressivePayTable[progPayTable][player->getRank()] > 0)
    {
        bool8 success;
        int32 potwinner;

        potwinner = sProgressivePayTable[progPayTable][player->getRank()];

		if (potwinner != 10 && potwinner != 100)
		{
			potwinner *= conn->getProgressiveFixedAmountMultiplier();
		}

        // kharmon.  06-11-07.  #12205.  If the potwinner is <= 100, then it's a percentage, not
        // an absolute amount.
        bool bPercentage = false;
        if(potwinner<=100)
        {
            bPercentage=true;
        }

        // luke 3/7/02: This now returns an error when a progressive win fails
        success = conn->setProgressiveWinner(potwinner, progressivePayout, multiBankPayout, bPercentage);

        (*multiBankPayout) *= 100;  // kharmon.  11-15-07.  #20294.  Need to do this for all wins, because we pass them into the db as millimoney, not money.

        if (potwinner == 10 || potwinner == 100)
        {
            conn->SendWinMail("Caribbean Stud", potwinner, *progressivePayout);
        }

        if (!success)
            return false;
    }
    else
        *progressivePayout = 0;

    switch (*outcome)
    {
        case O_DEALER_WON:            // dealer wins: -ante, -raise
            payouts[0] = -bet;
            payouts[1] = -(bet << 1);
            break;

        case O_PLAYER_WON:            // player wins: ante 1:1, raise paytable:1
            payouts[0] = bet;
            payouts[1] = (bet << 1) * sPayTable[payTable][player->getRank()];
            break;

        case O_PUSH:
            *useHighCard = false;
            payouts[0] = 0;
            payouts[1] = 0;
            break;
    }

    if (raised)
    {
        if (!dealer->qualifies())
        {
            // dealer doesn't qualify: ante 1:1, raise pushed
            //
            payouts[0] = bet;
            payouts[1] = 0;

            *outcome = O_DEALER_NOQUALIFY;
        }

        winnings = bet + payouts[0] + (bet << 1) + payouts[1];
    }
    else
    {
        // fold:  lose original bet
        //
        payouts[0] = -bet;
        payouts[1] = 0;
        winnings = 0;

        *outcome = O_PLAYER_FOLD;
    }

	// [AO 2007-10-18] Always award jackpot
	*balAdjust = winnings + *progressivePayout;

    return true;
}

void
CStudGame::save(CSavedGame* saved)
{
    // everything is already stored in the database (no need to store the shue)
    //
}

bool8
CStudGame::restore(CSavedGame* saved)
{
    setMalfunction(saved->bMalfunction);    // kharmon.  01-05-06.  #4250.  Restore the malfunction state.

    bet = saved->bet;
    progressiveBet = saved->progressiveBet;
    payTable = saved->payTable;
    progPayTable = conn->getProgressivePayTable();

    dealer = new CStudHand(&stdMgr);
    player = new CStudHand(&stdMgr);

    if(saved->bHasDeckSeed==false)
    {
        // kharmon.  10-12-06.  #8226.
        // older saved games won't have a deck seed
        dealer->restore(saved->dealerCards, saved->numDealerCards);
        player->restore(saved->playerCards, saved->numPlayerCards);
    }
    else
    {
        // kharmon.  10-12-06.  #8226.  Newer saved games regenerate the cards from 
        // the deck seed.
	    ShueGameInfo sgi;
	    sgi.gameId = CARIBBEAN_STUD;
	    sgi.machId = 0;

        Shue *shue = new Shue(&stdMgr, CSTUD_NUMDECKS, sgi);

        ShueSave save;
        save.seed = saved->nDeckSeed;
        save.next = 0;
        shue->restore(&save);

        player->add(shue->getCard());
        dealer->add(shue->getCard());

        player->add(shue->getCard());
        dealer->add(shue->getCard());

        player->add(shue->getCard());
        dealer->add(shue->getCard());

        player->add(shue->getCard());
        dealer->add(shue->getCard());

        player->add(shue->getCard());
        dealer->add(shue->getCard());

        delete shue;
    }


    //
    // At this point the server is *exactly* the same as when the game was
    // saved:  all automatic playing had already completed and the game
    // was idle.
    //

    // Make sure we have good hands
    return dealer->rankHand() && player->rankHand() && !hasMalfunction();     // kharmon.  01-05-06.  #4250.  The game is not "OK" if we have a malfunction.
}

void
CStudGame::send()
{
    //
    // This message is contained within another message
    // (don't call msgStart()/msgEnd()).
    //
    conn->msgAdd(bet);
    conn->msgAdd(progressiveBet);
    conn->msgAdd(payTable);
    conn->msgReleaseBuf(dealer->outlast(conn->msgGetBuf()));
    conn->msgAdd((uint8)(player->getCount() & 0xff));
    conn->msgReleaseBuf(player->out(conn->msgGetBuf()));
}


